/******************************************************************************
 * 
 * Announce: CSharpKit, Basic algorithms, components and definitions.
 *           Copyright (C) ShenYongchen.
 *           All rights reserved.
 *   Author: 申永辰.郑州 (shenyczz@163.com)
 *  WebSite: http://github.com/shenyczz/CSharpKit
 *
 * THIS CODE IS LICENSED UNDER THE MIT LICENSE (MIT).
 * THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
 * IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
 * PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
 * 
******************************************************************************/

using System;
using System.IO;

namespace CSharpKit.SVM
{
    /// <summary>
    /// 范围变换
    /// </summary>
    public class RangeTransform : IRangeTransform
    {
        #region Constructor

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="minValues">The minimum values in each dimension（每列最小值）</param>
        /// <param name="maxValues">The maximum values in each dimension（每列最大值）</param>
        /// <param name="lowerBound">The desired lower bound for all dimensions（期望下限值）</param>
        /// <param name="upperBound">The desired upper bound for all dimensions（期望上限值）</param>
        public RangeTransform(double[] minValues, double[] maxValues, double lowerBound, double upperBound)
        {
            _length = minValues.Length;
            if (maxValues.Length != _length)
                throw new Exception("Number of max and min values must be equal.");

            _inputStart = new double[_length];
            _inputScale = new double[_length];
            for (int i = 0; i < _length; i++)
            {
                _inputStart[i] = minValues[i];
                _inputScale[i] = maxValues[i] - minValues[i];
            }

            _outputStart = lowerBound;
            _outputScale = upperBound - lowerBound;
        }

        private RangeTransform(double[] inputStart, double[] inputScale, double outputStart, double outputScale, int length)
        {
            _inputStart = inputStart;
            _inputScale = inputScale;
            _outputStart = outputStart;
            _outputScale = outputScale;
            _length = length;
        }

        #endregion

        #region Fields

        private int _length;            // 数据集维度

        private double[] _inputStart;   // minValue
        private double[] _inputScale;   // 输入缩放 = maxValue - minValue

        private double _outputStart;    // lowerBound
        private double _outputScale;    // 输出缩放 = upperBound - lowerBound

        #endregion

        #region Constants

        /// <summary>
        /// Default lower bound for scaling (-1).
        /// </summary>
        public const int DEFAULT_LOWER_BOUND = -1;
        /// <summary>
        /// Default upper bound for scaling (1).
        /// </summary>
        public const int DEFAULT_UPPER_BOUND = 1;

        #endregion

        #region IRangeTransform Members

        /// <summary>
        /// Transforms this an input value using the scaling transform for the provided dimension.
        /// </summary>
        /// <param name="input">The input value to transform</param>
        /// <param name="index">The dimension whose scaling transform should be used</param>
        /// <returns>The scaled value</returns>
        public double Transform(double input, int index)
        {
            index--;    // 因为标签从1开始，base_1
            
            double tmp = input - _inputStart[index];
            if (_inputScale[index] == 0)
                return 0;

            tmp /= _inputScale[index];
            tmp *= _outputScale;
            return tmp + _outputStart;
        }

        /// <summary>
        /// Transforms the input array based upon the values provided.
        /// </summary>
        /// <param name="input">The input array</param>
        /// <returns>A scaled array</returns>
        public SvmNode[] Transform(SvmNode[] input)
        {
            SvmNode[] output = new SvmNode[input.Length];
            for (int i = 0; i < output.Length; i++)
            {
                int index = input[i].Index;
                double value = input[i].Value;
                output[i] = new SvmNode(index, Transform(value, index));
            }
            return output;
        }

        /// <summary>
        /// 确定所提供问题的范围转换<br/>
        /// Determines the Range transform for the provided problem.  Uses the default lower and upper bounds.
        /// </summary>
        /// <param name="prob">The Problem to analyze</param>
        /// <returns>The Range transform for the problem</returns>
        public static RangeTransform Compute(SvmProblem prob)
        {
            return Compute(prob, DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
        }

        #endregion


        /// <summary>
        /// 确定所提供问题的范围转换<br/>
        /// </summary>
        /// <param name="prob">The Problem to analyze</param>
        /// <param name="lowerBound">The lower bound for scaling</param>
        /// <param name="upperBound">The upper bound for scaling</param>
        /// <returns>The Range transform for the problem</returns>
        public static RangeTransform Compute(SvmProblem prob, double lowerBound, double upperBound)
        {
            // 计算数据集每个维度的最大最小值
            double[] minVals = new double[prob.MaxIndex];
            double[] maxVals = new double[prob.MaxIndex];
            for (int i = 0; i < prob.MaxIndex; i++)
            {
                minVals[i] = double.MaxValue;
                maxVals[i] = double.MinValue;
            }

            for (int i = 0; i < prob.Count; i++)
            {
                for (int j = 0; j < prob.X[i].Length; j++)
                {
                    int index = prob.X[i][j].Index - 1;
                    double value = prob.X[i][j].Value;
                    minVals[index] = System.Math.Min(minVals[index], value);
                    maxVals[index] = System.Math.Max(maxVals[index], value);
                }
            }

            for (int i = 0; i < prob.MaxIndex; i++)
            {
                if (minVals[i] == double.MaxValue || maxVals[i] == double.MinValue)
                {
                    // 无效值设为0
                    minVals[i] = 0;
                    maxVals[i] = 0;
                }
            }
            
            return new RangeTransform(minVals, maxVals, lowerBound, upperBound);
        }


        /// <summary>
        /// Writes this Range transform to a file.    This will overwrite any previous data in the file.
        /// /// </summary>
        /// <param name="outputFile">The file to write to</param>
        /// <param name="r">The Range to write</param>
        public static void Write(string outputFile, RangeTransform r)
        {
            FileStream fs = File.Open(outputFile, FileMode.Create);
            try
            {
                Write(fs, r);
            }
            finally
            {
                fs.Close();
            }
        }

        /// <summary>
        /// Writes this Range transform to a stream.
        /// </summary>
        /// <param name="stream">The stream to write to</param>
        /// <param name="r">The range to write</param>
        public static void Write(Stream stream, RangeTransform r)
        {
            TemporaryCulture.Start();

            StreamWriter output = new StreamWriter(stream);
            output.WriteLine(r._length);
            output.Write(r._inputStart[0]);
            for (int i = 1; i < r._inputStart.Length; i++)
                output.Write(" " + r._inputStart[i]);
            output.WriteLine();
            output.Write(r._inputScale[0]);
            for (int i = 1; i < r._inputScale.Length; i++)
                output.Write(" " + r._inputScale[i]);
            output.WriteLine();
            output.WriteLine("{0} {1}", r._outputStart, r._outputScale);
            output.Flush();

            TemporaryCulture.Stop();
        }


        /// <summary>
        /// Reads a Range transform from a file.
        /// </summary>
        /// <param name="inputFile">The file to read from</param>
        /// <returns>The Range transform</returns>
        public static RangeTransform Read(string inputFile)
        {
            FileStream s = File.OpenRead(inputFile);
            try
            {
                return Read(s);
            }
            finally
            {
                s.Close();
            }
        }

        /// <summary>
        /// Reads a Range transform from a stream.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>The Range transform</returns>
        public static RangeTransform Read(Stream stream)
        {
            TemporaryCulture.Start();

            StreamReader input = new StreamReader(stream);
            int length = int.Parse(input.ReadLine());
            double[] inputStart = new double[length];
            double[] inputScale = new double[length];
            string[] parts = input.ReadLine().Split();
            for (int i = 0; i < length; i++)
                inputStart[i] = double.Parse(parts[i]);
            parts = input.ReadLine().Split();
            for (int i = 0; i < length; i++)
                inputScale[i] = double.Parse(parts[i]);
            parts = input.ReadLine().Split();
            double outputStart = double.Parse(parts[0]);
            double outputScale = double.Parse(parts[1]);

            TemporaryCulture.Stop();

            return new RangeTransform(inputStart, inputScale, outputStart, outputScale, length);
        }

        //}}@@@
    }


}
